The unofficial DM2 format description Uwe Girlich, uwe@planetquake.com v1.0.1, 7/15/1998 This document describes the DM2 file format. This file format is the result of ``recording'' a game in Quake II. This documentation covers the Quake II versions 3.05 through 3.17. ______________________________________________________________________ Table of Contents 1. Introduction 1.1 Recording and Playback 1.2 Versions 1.3 Advertising 2. File structure 2.1 General remarks 2.2 Block of messages 2.3 Message 2.4 Auxiliary routines 3. List of all message types 3.1 bad 3.2 muzzleflash 3.3 muzzlflash2 3.4 temp_entity 3.5 layout 3.6 inventory 3.7 nop 3.8 disconnect 3.9 reconnect 3.10 sound 3.11 print 3.12 stufftext 3.13 serverdata 3.14 configstring 3.15 spawnbaseline 3.16 centerprint 3.17 download 3.18 playerinfo 3.19 packetentities 3.20 deltapacketentities 3.21 frame 4. Version History and Acknowledgements ______________________________________________________________________ 11.. IInnttrroodduuccttiioonn 11..11.. RReeccoorrddiinngg aanndd PPllaayybbaacckk To create a recording of your Quake II play start any map and then use the console command _r_e_c_o_r_d _n_a_m_e. This records the game play from your point of view into the file demos/name.dm2. To stop this recording use _s_t_o_p or even quit the whole game (_q_u_i_t). To play it back, use the command _d_e_m_o_m_a_p _n_a_m_e_._d_m_2 or simply _m_a_p _n_a_m_e_._d_m_2. To create a server side recording invoke at the server during a running game the console command _s_e_r_v_e_r_r_e_c_o_r_d _n_a_m_e. This records all information of all entities in the whole level but no player information into the file demos/name.dm2. These recordings are really huge! 11..22.. VVeerrssiioonnss In this document I'll discuss the DM2 format used by Quake II. There are significant differences between the Quake II test release (engine version 3.00) and the CD retail version (3.05). This difference consists especially in the reordered message code bytes, so that the ``normal codes'' have the values 1 to 5 in 3.05. There are some more differences in the ssppaawwnnbbaasseelliinnee message. Due to the existence of a sole DM2 file in the 3.00 format (packed in the PAK file of the test release) and considering the fact, that the test release can't even record new ones, I will confine myself to the ``new'' format, introduced with the CD retail version 3.05. With 3.15 a new kind of DM2 file was introduced: server side recordings. These are for DM2 editors only, since they contain all information from all entities but no player information and Quake II isn't even able to play them back. In 3.17 the server-side recordings became easier to parse because sseerrvveerrddaattaa can now be used to distinguish between a client- and server-side recording. _v_e_r_s_i_o_n platform note 3.05 x86 Nov 30 1997 RELEASE Win32 CD retail 3.06 x86 Dec 9 1997 RELEASE Win32 first patch 3.07 x86 Dec 27 1997 RELEASE Win32 interim release 3.08 x86 Dec 28 1997 RELEASE Win32 another interim release 3.09 x86 Dec 29 1997 RELEASE Win32 with patch program 3.10 x86 Jan 4 1998 RELEASE Win32 last before point release 3.10 NON-WIN32 Jan 5 1998 NON-WIN32 Linux first Linux release 3.12 x86 Feb 16 1998 RELEASE Win32 point release 3.13 x86 Feb 23 1998 RELEASE Win32 point fix release 3.13 i386 Feb 24 1998 RELEASE Linux point fix release 3.14 i386 Mar 2 1998 RELEASE Linux another fix release 3.14 x86 Mar 2 1998 RELEASE Win32 another fix release 3.14 i386 Mar 3 1998 RELEASE Linux bug fix release 3.14a 3.15 x86 May 27 1998 Win32 RELEASE Win32 mission pack release 3.15 i386 May 29 1998 Linux Linux bug fix release 3.15a 3.17 i386 Jun 21 1998 Linux Linux 3.17 x86 Jun 22 1998 Win32 RELEASE Win32 Covered Quake II versions. 11..33.. AAddvveerrttiissiinngg As the clever reader may know I'm the author of LMPC, the Little Movie Processing Centre. With this tool you may +o ``decompile'' an existing DM2 file (3.05 ... 3.17) to a simple text file and +o ``compile'' such a (modified) text file back to a binary DM2 file. With LMPC it is very easy to analyse a DM2 file but you may change it as well and so create a DM2 file of a Quake II game you never played. The current version of LMPC can be found at my Demo Specs page <http://www.planetquake.com/demospecs>. 22.. FFiillee ssttrruuccttuurree 22..11.. GGeenneerraall rreemmaarrkkss All multi-byte structures are Intel-ordered (low first). Descriptive sentences in _i_t_a_l_i_c_s are copied from the published game source. There are up to now 2 different source packages available from id Software: +o the older from december 1997 with the source of the game library and some tools: <ftp://ftp.idsoftware.com/pub/quake2/source/q2source_12_11.zip> and +o the newer one from march 1998 with the source of the game library only: <ftp://ftp.idsoftware.com/pub/quake2/source/q2-3.14-source.exe> I usually refer to the files from the newer source but use the directory hierarchy from the old source. 22..22.. BBlloocckk ooff mmeessssaaggeess A DM2 file is a set of ``blocks'' of ``messages''. A block consists of a 4 byte length entry and the actual messages: typedef struct { unsigned long size; unsigned char messages[size]; } block_t; #define MAX_MSGLEN 1400 is the maximum message size (real game data in a network packet). The value must be less or equal than 1472 to avoid fragmentation of the UDP packets on an Ethernet. The MTU in PPP is much smaller and QuakeWorld uses also much bigger network packets but the reduced packet size is for LAN play definitely a great improvement. In server side recordings there is not such a restriction. if (serverdata.isdemo == RECORD_CLIENT && block.size>MAX_MSGLEN) error("Demo message > MAX_MSGLEN"); Beginning with engine version 3.05 Quake II uses an empty block with a block size of -1 (0xffffffff) as the last block. This remindes (at least) me of the old DOOM LMP end byte (0x80). 22..33.. MMeessssaaggee This is the message structure: typedef struct { unsigned char ID; char messagecontent[???]; } message_t; The length of a message depends on its type (or ID) but it can't be bigger than MAX_MSGLEN, the size of a full block. 22..44.. AAuuxxiilliiaarryy rroouuttiinneess Here comes the definition of some small auxiliary routines to simplify the main message description. get_next_unsigned_char, get_next_signed_char, get_next_short and get_next_long, get_next_float are basic functions and they do exactly what they are called. Please note: byte, char or short will be converted to int. In the following I often use a count variable int i; without declaration. I hope this does not confuses you. int ReadChar { return (int) get_next_signed_char; } int ReadByte { return (int) get_next_unsigned_char; } int ReadShort { return (int) get_next_short; } int ReadLong { return (int)get_next_long; } float ReadFloat { return get_next_float; } The string reading stops at '\0' or after 0x7FF bytes. The internal buffer has only 0x800 bytes available. char* ReadString { char* string_pointer; char string_buffer[0x800]; string_pointer=string_buffer; for (i=0 ; i<0x7FF ; i++, string_pointer++) { if (! (*string_pointer = ReadChar) ) break; } *string_pointer = '\0'; return strdup(string_buffer); } ReadPosition(vec_t* pos) { for (i=0 ; i<3 ; i++) pos[i] = (vec_t) ReadShort * 0.125; } A direction for temporary entities is stored in a single byte. The 162 possible orientations are precalculated and stored in a template list. Each direction is represented by a normalised vector. The template list can be found in the source, utils3/qdata/anorms.h I tried to reproduce the template list but I didn't find the algorithm behind it. Take it as it is: even Quake II uses the list and calculates with dot products the best fitting direction. ReadDir(vec_t* pos) { #define NUMVERTEXNORMALS 162 int code; float avertexnormals[NUMVERTEXNORMALS][3] = { #include "utils3/qdata/anorms.h" } code = ReadByte; if (code >= NUMVERTEXNORMALS) error("MSF_ReadDir: out of range"); pos[0] = avertexnormals[code][0]; pos[1] = avertexnormals[code][1]; pos[2] = avertexnormals[code][2]; } Note: A signed angle in a single byte. There are only 256 possible direction to look into. vec_t ReadAngle { return (vec_t) ReadChar * 360.0 / 256.0; } vec_t ReadAngle16 { return (vec_t) ReadShort * 360.0 / 65536.0; } 33.. LLiisstt ooff aallll mmeessssaaggee ttyyppeess The easiest way to explain a message is to give a short C like program fragment to parse such a message. It is not really the same code base as in LMPC but it should be _v_e_r_y similar. Each message can be described by its ID or its name. 33..11.. bbaadd ID 0x00 ppuurrppoossee Something is bad. This message should never appear. ppaarrssee rroouuttiinnee error("CL_ParseServerMessage: Illegible server message\n"); 33..22.. mmuuzzzzlleeffllaasshh ID 0x01 ppuurrppoossee _m_u_z_z_l_e _f_l_a_s_h_e_s _/ _p_l_a_y_e_r _e_f_f_e_c_t_s. vvaarriiaabblleess long entity; is the player entity with the effect. long value; is the muzzle flash / player effect. The value should be one of the MZ_ constants (source, game/q_shared.h, 574 - 590). It may be ``OR''ed with #define MZ_SILENCED 128 if the player uses the Silencer. ppaarrssee rroouuttiinnee entity = ReadShort; value = ReadByte; 33..33.. mmuuzzzzllffllaasshh22 ID 0x02 ppuurrppoossee _m_o_n_s_t_e_r _m_u_z_z_l_e _f_l_a_s_h_e_s. vvaarriiaabblleess long entity; is the monster entity with the effect. long value; is the monster muzzle flash. The value should be one of the MZ2_ constants (source, game/q_shared.h, 596 - 747). ppaarrssee rroouuttiinnee entity = ReadShort; value = ReadByte; 33..44.. tteemmpp__eennttiittyy ID 0x03 ppuurrppoossee Spawns a temporary entity. _T_e_m_p _e_n_t_i_t_y _e_v_e_n_t_s _a_r_e _f_o_r _t_h_i_n_g_s _t_h_a_t _h_a_p_p_e_n _a_t _a _l_o_c_a_t_i_o_n _s_e_p_e_r_a_t_e _f_r_o_m _a_n_y _e_x_i_s_t_i_n_g _e_n_t_i_t_y_. _T_e_m_p_o_r_a_r_y _e_n_t_i_t_y _m_e_s_s_a_g_e_s _a_r_e _e_x_p_l_i_c_i_t_l_y _c_o_n_s_t_r_u_c_t_e_d _a_n_d _b_r_o_a_d_c_a_s_t_. vvaarriiaabblleess long entitytype; is the type of the temporary entity. entitytype should be one of the TE_ constants (source, game/q_shared.h, 758 - 788). They are defined with a typedef and not as #define but who cares? There are many different kinds of temporary entities in Quake II: ppooiinntt eennttiittyy is a point like entity like explosions or teleport fog. It needs a position. #define TE_EXPLOSION1 5 boss, barrels etc. explosion #define TE_EXPLOSION2 6 barrels explosion #define TE_ROCKET_EXPLOSION 7 rocket explosion #define TE_GRENADE_EXPLOSION 8 grenade explosion #define TE_ROCKET_EXPLOSION_WATER 17" rocket explosion under water #define TE_GRENADE_EXPLOSION_WATER 18" grenade explosion under water #define TE_BFG_EXPLOSION 20 BFG explosion #define TE_BFG_BIGEXPLOSION 21 big BFG explosion #define TE_BOSSTPORT 22 boss teleports away iimmppaacctt eennttiittyy is a small point like entity, spawned on an impact of a bullet etc. It needs a position (often a trace end) and a direction from there. #define TE_GUNSHOT 0 hit with Machine Gun and Chain Gun #define TE_BLOOD 1 clients and monsters bleed when hit #define TE_BLASTER 2 hit with Blaster #define TE_SHOTGUN 4 hit with Shotgun #define TE_SPARKS 9 hit someone #define TE_SCREEN_SPARKS 12 hit on an Energy Armor #define TE_SHIELD_SPARKS 13 hit on a different Energy Armor #define TE_BULLET_SPARKS 14 hit someone with bullets #define TE_GREENBLOOD 27 new in 3.12, not used lliinnee eennttiittyy is a 2 dimensional entity. It needs an origin and an end position. #define TE_RAILTRAIL 3 trail of the Rail Gun (blue) #define TE_BUBBLETRAIL 11 shot with a bullet through water #define TE_BFG_LASER 23 new in 3.12, BFG laser #define TE_PLASMATRAIL 26 new in 3.12, not used ssppeecciiaall eennttiittyy is a entity which doesn't fit into the other categories. #define TE_SPLASH 10 creates a particle splash effect #define TE_LASER_SPARKS 15 laser hits a wall #define TE_PARASITE_ATTACK 16 parasite attack #define TE_MEDIC_CABLE_ATTACK 19" medic cable attac #define TE_GRAPPLE_CABLE 24 new in 3.12, used in CFT #define TE_WELDING_SPARKS 25 new in 3.12, not used #define TE_28 28 new in 3.15, unknown #define TE_29 29 new in 3.15, unknown The sounds field of the special TE_SPLASH temporary entity should be one of the SPLASH_ constants (source, game/q_shared.h, 790 - 796). vec3_t origin; is the origin of the temporary entity. vec3_t trace_endpos; is the end position of the line like temporary entity. vec3_t offset; is an additional offset for the CTF grappling hook. vec3_t movedir; is the moving direction for the temporary entity. int count; is a number for multi-particle temporary entities. int color; is the colour of the ``Laser Sparks''. ppaarrssee rroouuttiinnee entitytype = ReadByte; switch (entitytype) { // point entity case TE_EXPLOSION1: case TE_EXPLOSION2: case TE_ROCKET_EXPLOSION: case TE_GRENADE_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: case TE_GRENADE_EXPLOSION_WATER: case TE_BFG_EXPLOSION: case TE_BFG_BIGEXPLOSION: case TE_BOSSTPORT: case TE_28: ReadPosition(origin); break; // impact entity case TE_GUNSHOT: case TE_BLOOD: case TE_BLASTER: case TE_SHOTGUN: case TE_SPARKS: case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: case TE_BULLET_SPARKS: case TE_GREENBLOOD: ReadPosition(origin); ReadDir(movedir); break; // line entity case TE_RAILTRAIL: case TE_BUBBLETRAIL: case TE_BFG_LASER: case TE_PLASMATRAIL: ReadPosition(origin); ReadPosition(trace_endpos); break; // special entity case TE_SPLASH: count = ReadByte; ReadPosition(origin); ReadDir(movedir); sounds = ReadByte; break; case TE_LASER_SPARKS: case TE_WELDING_SPARKS: case TE_29: count = ReadByte; ReadPosition(origin); ReadDir(movedir); color = ReadByte; break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: entity = ReadShort; ReadPosition(origin); ReadPosition(trace_endpos); break; case TE_GRAPPLE_CABLE: entity = ReadShort; ReadPosition(origin); ReadPosition(trace_endpos); ReadPosition(offset); break; default: error("CL_ParseTEnt: bad type"); break; } 33..55.. llaayyoouutt ID 0x04 ppuurrppoossee This message displays the Field Computer (``cmd help'', bound to F1). It contains the summary screen (Deathmatch Scoreboard or single player secrets, goals etc.). It stores the message only on the client siede. The actual display will be triggered by the ppllaayyeerriinnffoo message with a special stats command. vvaarriiaabbllee char text[MAX_MSGLEN]; is the summary screen text with some kind of control characters. The control language is very simple. Read some examples in the source, game/p_hud.c, functions DeathmatchScoreboardMessage and HelpComputer. ppaarrssee rroouuttiinnee text = ReadString; 33..66.. iinnvveennttoorryy ID 0x05 ppuurrppoossee Tells the clients its inventory. vvaarriiaabbllee int inventory[MAX_ITEMS]; is the player's inventory array. #define MAX_ITEMS 256 is the number different items in the inventory. The inventory concept of Quake II is very open and expandable. In an global array (source, game/g_items.c, 1100 - 2041) gitem_t itemlist[]; is the list of all possible things a player can pickup and carry around. This array will be filled at compile time. In the inventory array are stored how many of each thing of the item- list a players carries. The server tells the meaning of each index with ccoonnffiiggssttrriinngg messages. I give the original table for itemlist. Every modification can change it totally and it changed indeed from 3.05 to 3.12. value purpose 0 not used 1 Body Armor 2 Combat Armor 3 Jacket Armor 4 Armor Shard 5 Power Screen 6 Power Shield 7 Blaster 8 Shotgun 9 Super Shotgun 10 Machinegun 11 Chaingun 12 Grenades 13 Grenade Launcher 14 Rocket Launcher 15 HyperBlaster 16 Railgun 17 BFG10K 18 Shells 19 Bullets 20 Cells 21 Rockets 22 Slugs 23 Quad Damage 24 Invulnerability 25 Silencer 26 Rebreather 27 Environment Suit 28 Ancient Head 29 Adrenaline 30 Bandolier 31 Ammo Pack 32 Data CD 33 Power Cube 34 Pyramid Key 35 Data Spinner 36 Security Pass 37 Blue Key 38 Red Key 39 Commander's Head 40 Airstrike Marker 41 Health Standard inventory entries. ppaarrssee rroouuttiinnee for (i=0 ; i<MAX_ITEMS ; i++) inventory[i] = ReadShort; 33..77.. nnoopp ID 0x06 ppuurrppoossee No operation. ppaarrssee rroouuttiinnee none 33..88.. ddiissccoonnnneecctt ID 0x07 ppuurrppoossee Diconnect from the server. ppaarrssee rroouuttiinnee print("Server disconnected\n"); 33..99.. rreeccoonnnneecctt ID 0x08 ppuurrppoossee Reconnect to the server. ppaarrssee rroouuttiinnee print("Server disconnected, reconnecting\n"); 33..1100.. ssoouunndd ID 0x09 ppuurrppoossee Plays a sound. vvaarriiaabblleess long mask; is a bit mak to reduce the network traffic. long soundnum; is the index in the precache sound table. float vol; is the volume of the sound (0.0 off, 1.0 max). float attenuation; is the attenuation of the sound. attenuation should have one of the ATTN_ constants (source, game/q_shared.h, 813 - 816). #define ATTN_NONE 0 _f_u_l_l _v_o_l_u_m_e _t_h_e _e_n_t_i_r_e _l_e_v_e_l #define ATTN_NORM 1 the normal attenuation #define ATTN_IDLE 2 for idle monsters #define ATTN_STATIC 3 _d_i_m_i_n_i_s_h _v_e_r_y _r_a_p_i_d_l_y _w_i_t_h _d_i_s_t_a_n_c_e float timeofs; is the offset in seconds between the frame start and the sound start. It is 0 in the entire source. long channel; is the sound channel. There are 8 possible sound channels for each entity in Quake II (0-7) but it uses 5 only. channel should be one of the CHAN_ constants (source, game/q_shared.h, 802 - 806). #define CHAN_AUTO 0 selects a channel automatically #define CHAN_WEAPON 1 weapon use sounds #define CHAN_VOICE 2 pain calls #define CHAN_ITEM 3 item get sounds #define CHAN_BODY 4 jump and fall sounds long entity; is the entity which caused the sound. The maximum value of entity is #define MAX_EDICTS 1024 . vec3_t origin; is the origin of the sound. ppaarrssee rroouuttiinnee long entity_channel; // combined variable mask = ReadByte; soundnum = ReadByte; vol = (mask & 0x01) ? ((float)ReadByte / 255.0) : (1.0); attenuation = (mask & 0x02) ? ((float)ReadByte / 64.0) : (1.0); timeofs = (mask & 0x10) ? ((float)ReadByte * 0.001) : (0.0); if (mask & 0x08) { entity_channel = ReadShort; entity = (entity_channel >> 3); channel = entity_channel & 0x07; if (entity > MAX_EDICTS) { error("CL_ParseStartSoundPacket: ent = %i", entity); } } else { channel = 0; entity = 0; } if (mask & 0x04) { ReadPosition(origin); } 33..1111.. pprriinntt ID 0x0A ppuurrppoossee Prints a text at the top of the screen. vvaarriiaabblleess long level; is the print level. level should be one of the following: #define PRINT_LOW 0 pickup messages #define PRINT_MEDIUM 1 death messages #define PRINT_HIGH 2 critical messages #define PRINT_CHAT 3 chat messages char string[MAX_MESSAGE_SIZE]; is the the text to be displayed. ppaarrssee rroouuttiinnee level = ReadByte; if (level == PRINT_CHAT) sound("misc/talk.wav"); string = ReadString; 33..1122.. ssttuufffftteexxtt ID 0x0B ppuurrppoossee The client transfers the text to the console and runs it. vvaarriiaabblleess char* text; is the command, which the client has to execute. ppaarrssee rroouuttiinnee text=ReadString; 33..1133.. sseerrvveerrddaattaa ID 0x0C ppuurrppoossee Set some global info. vvaarriiaabblleess long serverversion; is the protocol version coming from the server. game protocol 3.00 25 3.05 26 3.06 26 3.07 27 3.08 27 3.09 28 3.10 30 3.12 31 3.13 31 3.14 31 3.15 32 3.17 33 Quake II values for PROTOCOL_VERSION. long key; some kind of key. Will be used again later in a ssttuufffftteexxtt message for the login hand-shake. long isdemo; indicates the demo type. Possible values are: #define RECORD_NETWORK 0 data actually over the wire (proxy) #define RECORD_CLIENT 1 recorded on the client side. Containes information in the direct neighborhood of the recording player. #define RECORD_SERVER 2 recorded on the server side. Containes all information on all entities in the level.These files tend to become very large. They can't be played back by Quake II directly. This value appears in 3.17 but server side recordings work from 3.15 on. This variable is sometimes called attractloop for whatever reason. char* game; is the game directory (may be empty). long client; is the client id. char* mapname; is the name of the map. int compatible; compatibility with the CD retail version 3.05 (protocol 26). How to set this? ??FIXME?? ppaarrssee rroouuttiinnee log("Serverdata packet received.\n"); serverversion = ReadLong; if (!compatible) { if (serverversion != PROTOCOL_VERSION) error("Server returned version %i, not %i", serverversion, PROTOCOL_VERSION); } } key = ReadLong; isdemo = ReadByte; game = ReadString; client = ReadShort; mapname = ReadString; 33..1144.. ccoonnffiiggssttrriinngg ID 0x0D ppuurrppoossee _c_o_n_f_i_g _s_t_r_i_n_g_s _a_r_e _a _g_e_n_e_r_a_l _m_e_a_n_s _o_f _c_o_m_m_u_n_i_c_a_t_i_o_n _f_r_o_m _t_h_e _s_e_r_v_e_r _t_o _a_l_l _c_o_n_n_e_c_t_e_d _c_l_i_e_n_t_s_. _E_a_c_h _c_o_n_f_i_g _s_t_r_i_n_g _c_a_n _b_e _a_t _m_o_s_t MAX_QPATH characters. vvaarriiaabblleess int index; is the number of the config string. The following constants (source, game/q_shared.h, 875 - 891) determine where to find something in the full array of config strings. #define CS_NAME 0 is the name of the level. #define CS_CDTRACK 1 is the audio CD track for this level. #define CS_SKY 2 is the sky texture. #define CS_SKYAXIS 3 is the sky axis in the _%_f _%_f _%_f _f_o_r_m_a_t. #define CS_SKYROTATE 4 is the rotation speed in the format %f. #define CS_STATUSBAR 5 is the start of the list of _d_i_s_p_l_a_y _p_r_o_g_r_a_m _s_t_r_i_n_gs for the statusbar. #define CS_MAXCLIENTS 30 is the current maximum number of clients on a server. #define CS_MAPCHECKSUM 31 is the map checksum _f_o_r _c_a_t_c_h_i_n_g _c_h_e_a_t_e_r _m_a_p_s. #define CS_MODELS 32 is the start of the model precache list. #define MAX_MODELS 256 is the maximum number of the models in the precache list. #define CS_SOUNDS (CS_MODELS+MAX_MODELS) (288) is the start of the sound precache list. #define MAX_SOUNDS 256 is the maximum number of the sounds in the precache list. #define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) (544) is the start of the image list. #define MAX_IMAGES 256 is the maximum numer of the images. #define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) (800) is the start of the light styles list. #define MAX_LIGHTSTYLES 256 is the maximum number of the light styles. #define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) (1056) is the start of the items list. #define MAX_ITEMS 256 is the maximum number of items in the inventory list. #define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) (1312) is the start of the player skin list. #define MAX_CLIENTS 256 is the maximum number of players. #define MAX_CONFIGSTRINGS (CS_PLAYERSKINS+MAX_CLIENTS) (1568) is the maximum number of config strings. char string[MAX_QPATH]; is the corresponding config string. #define MAX_QPATH 64 is the maximum length of a config string. ppaarrssee rroouuttiinnee index = ReadShort; if (index > MAX_CONFIGSTRINGS) error("configstring > MAX_CONFIGSTRINGS"); string = ReadString; 33..1155.. ssppaawwnnbbaasseelliinnee ID 0x0E ppuurrppoossee Spawns a new entity. vvaarriiaabblleess long mask; is a bit-mask to reduce the network traffic. long entity; is the number of the entity. vec3_t origin; is the origin. vec3_t angles; is the orientation. vec3_t old_origin; is the old origin _f_o_r _l_e_r_p_i_n_g. It is used for client-side prediction and interpolation calculations. To compress a DM2 file, just leave it out, if the last frame had a origin entry for the entity in question. It is used with ccll__nnooddeellttaa 11 only. From 3.17 on old_origin is used for player entities only. long modelindex; is the model index. long modelindex2; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long modelindex3; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long modelindex4; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long frame; is the frame of the model. long skin; is the number of the skin for the model. long effects; _E_f_f_e_c_t_s _a_r_e _t_h_i_n_g_s _h_a_n_d_l_e_d _o_n _t_h_e _c_l_i_e_n_t _s_i_d_e _(_l_i_g_h_t_s_, _p_a_r_t_i_c_l_e_s_, _f_r_a_m_e _a_n_i_m_a_t_i_o_n_s_) _t_h_a_t _h_a_p_p_e_n _c_o_n_s_t_a_n_t_l_y _o_n _t_h_e _g_i_v_e_n _e_n_t_i_t_y_. _A_n _e_n_t_i_t_y _t_h_a_t _h_a_s _e_f_f_e_c_t_s _w_i_l_l _b_e _s_e_n_t _t_o _t_h_e _c_l_i_e_n_t _e_v_e_n _i_f _i_t _h_a_s _a _z_e_r_o _i_n_d_e_x _m_o_d_e_l_. The bit-mask effects holds the EF_ constants (source, game/q_shared.h, 529 - 549).) long renderfx; are some special render flags. The bit-mask renderfx holds the RF_ constants (source, game/q_shared.h, 553 - 565). long solid; _f_o_r _c_l_i_e_n_t _s_i_d_e _p_r_e_d_i_c_t_i_o_n_, _8_*_(_b_i_t_s _0_-_4_) _i_s _x_/_y _r_a_d_i_u_s _8_*_(_b_i_t_s _5_-_9_) _i_s _z _d_o_w_n _d_i_s_t_a_n_c_e_, _8_(_b_i_t_s_1_0_-_1_5_) _i_s _z _u_p long sound; _f_o_r _l_o_o_p_i_n_g _s_o_u_n_d_s_, _t_o _g_u_a_r_a_n_t_e_e _s_h_u_t_o_f_f long event; _i_m_p_u_l_s_e _e_v_e_n_t_s _-_- _m_u_z_z_l_e _f_l_a_s_h_e_s_, _f_o_o_t_s_t_e_p_s_, _e_t_c _e_v_e_n_t_s _o_n_l_y _g_o _o_u_t _f_o_r _a _s_i_n_g_l_e _f_r_a_m_e_, _t_h_e_y _a_r_e _a_u_t_o_m_a_t_i_c_a_l_l_y _c_l_e_a_r_e_d _e_a_c_h _f_r_a_m_e event should have one of the following values (source, game/q_shared.h, 901 - 910): typedef enum { EV_NONE, // 0 EV_ITEM_RESPAWN, // 1 EV_FOOTSTEP, // 2 EV_FALLSHORT, // 3 EV_FALL, // 4 EV_FALLFAR, // 5 EV_PLAYER_TELEPORT // 6 } entity_event_t; ppaarrssee rroouuttiinnee mask = ReadByte; if (mask & 0x00000080) mask |= (ReadByte << 8); if (mask & 0x00008000) mask |= (ReadByte << 16); if (mask & 0x00800000) mask |= (ReadByte << 24); entity = (mask & 0x00000100) ? ReadShort : ReadByte; if (mask & 0x00000800) modelindex = ReadByte; if (mask & 0x00100000) modelindex2 = ReadByte; if (mask & 0x00200000) modelindex3 = ReadByte; if (mask & 0x00400000) modelindex4 = ReadByte; if (mask & 0x00000010) frame = ReadByte; if (mask & 0x00020000) frame = ReadShort; if (mask & 0x00010000) { if (mask & 0x02000000) skin = ReadLong; else skin = ReadByte; } else { if (mask & 0x02000000) skin = ReadShort; } if (mask & 0x00004000) { if (mask & 0x00080000) effects = ReadLong; else effects = ReadByte; } else { if (mask & 0x00080000) effects = ReadShort; } if (mask & 0x00001000) { if (mask & 0x00040000) renderfx = ReadLong; else renderfx = ReadByte; } else { if (mask & 0x00040000) renderfx = ReadShort; } if (mask & 0x00000001) origin[0] = ReadCoord; if (mask & 0x00000002) origin[1] = ReadCoord; if (mask & 0x00000200) origin[2] = ReadCoord; if (mask & 0x00000400) angles[0] = ReadAngle; if (mask & 0x00000004) angles[1] = ReadAngle; if (mask & 0x00000008) angles[2] = ReadAngle; if (mask & 0x01000000) ReadPosition(old_origin); if (mask & 0x04000000) sound = ReadByte; event = (mask & 0x00000020) ? ReadByte : 0; if (mask & 0x08000000) solid = ReadShort; 33..1166.. cceenntteerrpprriinntt ID 0x0F ppuurrppoossee Prints the specified text at the centre of the screen. There is only one text line with a maximum of 40 characters. To print more than this one line, use `\n' in a single cceenntteerrpprriinntt message for a new line. Every text line (the first 40 characters) will be centred horizontally. vvaarriiaabblleess char* text; is the text to be displayed. ppaarrssee rroouuttiinnee text = ReadString; 33..1177.. ddoowwnnllooaadd ID 0x10 ppuurrppoossee Download a file (sound, model etc.) from the server. It needs at least version 3.15 to transfer any data. There is no position information in the packet, so the client can't rearrange packets, which arrive in the wrong order. Therefore all download network packets are reliable packets and they need the usual acknowledgement. vvaarriiaabblleess long size; is the number of bytes transferred in the current packet. long percent; is the total amount sended in percent. char* downloadbuffer; is a buffer for downloaded files. char* filename; is the name for the downloaded file. How does the client know it? ??FIXME?? FILE* fp; is the file pointer for the downloaded file. ppaarrssee rroouuttiinnee size = ReadShort; percent=ReadByte; if (size == -1) { error("File not found.\n"); } if (serverdata.serverversion >= 32) { /* from version 3.15 on */ if (percent == 0) { fp = fopen(filename, "wb"); } for ( i=0 ; i<size ; i++ ) { downloadbuffer[i] = ReadByte; } fwrite(fp, size, 1, downloadbuffer); if (percent != 100) { servercommand("nextdl"); /* ask for the next part */ } else { fclose(fp); } } 33..1188.. ppllaayyeerriinnffoo ID 0x11 ppuurrppoossee Player info. _H_a_v_e _t_o come directly after a ffrraammee message in client recording. There is no such message in server side recordings since there is no special player who does the recording. vvaarriiaabblleess long mask; is a bit mask to reduce the network traffic. long mask2; is a bit mask to reduce the network traffic. long pm_type; is important for client side prediction and should be one of the PM_ constants (source, game/q_shared.h, 436 - 445). vec3_t origin; is the origin. vec3_t velocity; is the velocity. byte pm_flags; _d_u_c_k_e_d_, _j_u_m_p___h_e_l_d_, _e_t_c and should be one of the PMF_ constants (source, game/q_shared.h, 448 - 454). byte pm_time; is unknown (_e_a_c_h _u_n_i_t _= _8 _m_s). short gravity; is the gravity (800, cvar sv_gravity). vec3_t delta_angles; _a_d_d _t_o _c_o_m_m_a_n_d _a_n_g_l_e_s _t_o _g_e_t _v_i_e_w _d_i_r_e_c_t_i_o_n_, _c_h_a_n_g_e_d _b_y _s_p_a_w_n_s_, _r_o_t_a_t_i_n_g _o_b_j_e_c_t_s_, _a_n_d _t_e_l_e_p_o_r_t_e_r_s vec3_t viewangles; _f_o_r _f_i_x_e_d _v_i_e_w_s vec3_t viewoffset; _a_d_d _t_o pmovestate->origin vec3_t kick_angles; _a_d_d _t_o _v_i_e_w _d_i_r_e_c_t_i_o_n _t_o _g_e_t _r_e_n_d_e_r _a_n_g_l_e_s _s_e_t _b_y _w_e_a_p_o_n _k_i_c_k_s_, _p_a_i_n _e_f_f_e_c_t_s_, _e_t_c vec3_t gunangles; direction of weapon vec3_t gunoffset; offset of weapon int gunindex; model index of weapon int gunframe; frame of weapon float blend[4]; _r_g_b_a _f_u_l_l _s_c_r_e_e_n _e_f_f_e_c_t long fov; _h_o_r_i_z_o_n_t_a_l _f_i_e_l_d _o_f _v_i_e_w. Since the field of view is no client side variable (as it is in Quake), it is much easier to create zoom effects in movies. int rdflags; _r_e_f_d_e_f _f_l_a_g_s and should be one of the RDF_ constants (source, game/q_shared.h, 568 - 569). short stats[MAX_STATS]; _f_a_s_t _s_t_a_t_u_s _b_a_r _u_p_d_a_t_e_s Each entry in this array stays for something on the statusbar. The value of an icon entry is the image index defined in a ccoonnffiiggssttrriinngg message. A value of 0 on an icon entry switches the icon off. (source, game/q_shared.h, 820 - 837) #define MAX_STATS 32 maximum number of things in the status bar #define STAT_HEALTH_ICON 0 health icon image #define STAT_HEALTH 1 health value #define STAT_AMMO_ICON 2 ammo icon image #define STAT_AMMO 3 ammo value #define STAT_ARMOR_ICON 4 armour icon #define STAT_ARMOR 5 armour value #define STAT_SELECTED_ICON 6 icon image of the selected weapon #define STAT_PICKUP_ICON 7 icon image of a recently gotten thing #define STAT_PICKUP_STRING 8 ccoonnffiiggssttrriinngg index to describe the recently gotten thing, 0 = string off #define STAT_TIMER_ICON 9 icon image of timed object (Quad Damage, Invulnerability etc.) #define STAT_TIMER 10 timer value for a timed object (in seconds) #define STAT_HELPICON 11 help icon: 0 off, 1 on #define STAT_SELECTED_ITEM 12 item number of the selected weapon #define STAT_LAYOUTS 13 layout activator: 0 off, 1 display help/summary screen, 2 display inventory #define STAT_FRAGS 14 client score value #define STAT_FLASHES 15 _f_l_a_s_h _t_h_e _b_a_c_k_g_r_o_u_n_d_s _b_e_h_i_n_d _t_h_e _s_t_a_t_u_s _n_u_m_b_e_r_s_, _c_l_e_a_r_e_d _e_a_c_h _f_r_a_m_e_, _1 _= _h_e_a_l_t_h_, _2 _= _a_r_m_o_r, 0 = off ppaarrssee rroouuttiinnee mask = ReadShort; if (mask & 0x0001) pm_type = ReadByte; if (mask & 0x0002) ReadPostion(origin); if (mask & 0x0004) ReadPosition(velocity); if (mask & 0x0008) teleport_time = ReadByte; if (mask & 0x0010) pm_flags = ReadByte; if (mask & 0x0020) gravity = ReadShort; if (mask & 0x0040) { delta_angles[0] = ReadAngle16; delta_angles[1] = ReadAngle16; delta_angles[2] = ReadAngle16; } if (mask & 0x0080) { viewoffset[0] = ReadChar / 4.0; viewoffset[1] = ReadChar / 4.0; viewoffset[2] = ReadChar / 4.0; } if (mask & 0x0100) { viewangles[0] = ReadAngle16; viewangles[1] = ReadAngle16; viewangles[2] = ReadAngle16; } if (mask & 0x0200) { kick_angles[0] = ReadChar / 4.0; kick_angles[1] = ReadChar / 4.0; kick_angles[2] = ReadChar / 4.0; } if (mask & 0x1000) gunindex = ReadByte; if (mask & 0x2000) { gunframe = ReadByte; gunoffset[0] = ReadChar / 4.0; gunoffset[1] = ReadChar / 4.0; gunoffset[2] = ReadChar / 4.0; gunangles[0] = ReadChar / 4.0; gunangles[1] = ReadChar / 4.0; gunangles[2] = ReadChar / 4.0; } if (mask & 0x0400) { blend[0] = ReadByte / 255.0; blend[1] = ReadByte / 255.0; blend[2] = ReadByte / 255.0; blend[3] = ReadByte / 255.0; } if (mask & 0x0800) fov = ReadByte; if (mask & 0x4000) rdflags = ReadByte; mask2 = ReadLong; for (i=0;i<32;i++) if (mask2 & (0x00000001 << i)) stats[i] = ReadShort; 33..1199.. ppaacckkeetteennttiittiieess ID 0x12 ppuurrppoossee Entity updates. _H_a_v_e _t_o come in a client side recording directly after a ppllaayyeerriinnffoo message and in a server side recording directly after a ffrraammee message. This message is in fact a list of ssppaawwnnbbaasseelliinnee messages. Look there for a longer variable description. The list ends with entity==0. vvaarriiaabblleess long mask; is used to reduce the network traffic. long entity; is the number of the entity. long remove; indicates a disappearing entity. vec3_t origin; is the origin. vec3_t angles; is the orientation. vec3_t old_origin; old origin (for client side prediction). long modelindex; is the model index. long modelindex2; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long modelindex3; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long modelindex4; is _w_e_a_p_o_n_s_, _C_T_F_, _f_l_a_g_s_, _e_t_c. long frame; is the frame of the model. long skin; is the number of the skin for the model. long effects; is the entity effect. long renderfx; are some special render flags. long solid; _f_o_r _c_l_i_e_n_t _s_i_d_e _p_r_e_d_i_c_t_i_o_n_, _8_*_(_b_i_t_s _0_-_4_) _i_s _x_/_y _r_a_d_i_u_s _8_*_(_b_i_t_s _5_-_9_) _i_s _z _d_o_w_n _d_i_s_t_a_n_c_e_, _8_(_b_i_t_s_1_0_-_1_5_) _i_s _z _u_p long sound; _f_o_r _l_o_o_p_i_n_g _s_o_u_n_d_s_, _t_o _g_u_a_r_a_n_t_e_e _s_h_u_t_o_f_f long event; _i_m_p_u_l_s_e _e_v_e_n_t_s _-_- _m_u_z_z_l_e _f_l_a_s_h_e_s_, _f_o_o_t_s_t_e_p_s_, _e_t_c _e_v_e_n_t_s _o_n_l_y _g_o _o_u_t _f_o_r _a _s_i_n_g_l_e _f_r_a_m_e_, _t_h_e_y _a_r_e _a_u_t_o_m_a_t_i_c_a_l_l_y _c_l_e_a_r_e_d _e_a_c_h _f_r_a_m_e ppaarrssee rroouuttiinnee for (;;) { mask = ReadByte; if (mask & 0x00000080) mask |= (ReadByte << 8); if (mask & 0x00008000) mask |= (ReadByte << 16); if (mask & 0x00800000) mask |= (ReadByte << 24); entity = (mask & 0x00000100) ? ReadShort : ReadByte; if (entity >= MAX_EDICTS) error("CL_ParsePacketEntities: bad number:%i",entity); if (entity == 0) break; remove = (mask & 0x00000040) ? 1 : 0; if (mask & 0x00000800) modelindex = ReadByte; if (mask & 0x00100000) modelindex2 = ReadByte; if (mask & 0x00200000) modelindex3 = ReadByte; if (mask & 0x00400000) modelindex4 = ReadByte; if (mask & 0x00000010) frame = ReadByte; if (mask & 0x00020000) frame = ReadShort; if (mask & 0x00010000) { if (mask & 0x02000000) skin = ReadLong; else skin = ReadByte; } else { if (mask & 0x02000000) skin = ReadShort; } if (mask & 0x00004000) { if (mask & 0x00080000) effects = ReadLong; else effects = ReadByte; } else { if (mask & 0x00080000) effects = ReadShort; } if (mask & 0x00001000) { if (mask & 0x00040000) renderfx = ReadLong; else renderfx = ReadByte; } else { if (mask & 0x00040000) renderfx = ReadShort; } if (mask & 0x00000001) origin[0] = ReadCoord; if (mask & 0x00000002) origin[1] = ReadCoord; if (mask & 0x00000200) origin[2] = ReadCoord; if (mask & 0x00000400) angles[0] = ReadAngle; if (mask & 0x00000004) angles[1] = ReadAngle; if (mask & 0x00000008) angles[2] = ReadAngle; if (mask & 0x01000000) ReadPosition(old_origin); if (mask & 0x04000000) sound = ReadByte; event = (mask & 0x00000020) ? ReadByte : 0; if (mask & 0x08000000) solid = ReadShort; } 33..2200.. ddeellttaappaacckkeetteennttiittiieess ID 0x13 ppuurrppoossee Entity updates. May come after a ppaacckkeetteennttiittiieess block. ppaarrssee rroouuttiinnee unknown ??FIXME?? 33..2211.. ffrraammee ID 0x14 ppuurrppoossee Sequence numbers to cope with UDP packet loss, delta encoding and portal border crossings. Start of the ppllaayyeerriinnffoo and ppaacckkeetteennttiittiieess messages. In server side recordings this message contains only the current frame number. vvaarriiaabblleess long seq1; is the sequence number of the current packet or frame. Since the Quake II server uses a fixed time gap of 100ms (10Hz) between game state changes seq1 / 10 is the time in seconds since the server started. long seq2; is the sequence number of the delta reference frame. The reference holds for both following ppllaayyeerriinnffoo and ppaacckkeetteennttiittiieess messages. The Quake II server has a table with some old entity states and get from each client the sequence number of frames, which arrived correctly at the client side. So the server can decide which most currently sended old frame should be used now as the delta encoding reference frame. seq2 is -1 to reference to the ssppaawwnnbbaasseelliinnee values. long count; is the number of bytes in the areas array. #define MAX_MAP_AREAS 256 is the maximum number of areas in a map. unsigned char areas[MAX_MAP_AREAS / 8]; is the array which defines the areas to be rendered. Each bit in the areas array stays for one area in the map. long frame; is the frame number in server side recordings and similar to seq1. I may rename it even to seq1 in a future revision. ppaarrssee rroouuttiinnee long uk_b1; if (serverdata.isdemo == RECORD_CLIENT || serverdata.isdemo == RECORD_NETWORK) { seq1 = ReadLong; seq2 = ReadLong; if (serverdata.serverversion != 26) uk_b1 = ReadByte; count = ReadByte; for (i=0;i<count;i++) areas[i] = ReadByte; } if (serverdata.isdemo == RECORD_SERVER) { frame = ReadLong; } 44.. VVeerrssiioonn HHiissttoorryy aanndd AAcckknnoowwlleeddggeemmeennttss 00..00..11,, 1133 DDeecceemmbbeerr,, 11999977 +o First version (working paper) completed. +o Only based on the published game source. +o Never ever published. 00..00..22,, 2233 DDeecceemmbbeerr,, 11999977 +o Old 3.00 ID codes included. +o Version info included. +o All simple ID codes ok. +o packetentities, deltapacketentities, playerinfo missing. +o spawnbaseline is very difficult. +o Never published. 00..00..33,, 2277 DDeecceemmbbeerr,, 11999977 +o Old 3.00 ID codes removed. Nobody wants to know them. +o spawnbaseline is ok now. +o playerinfo ok. +o packetentities should be ok. +o ReadDir for temp_entity ok. +o deltapacketentities missing. +o Never published. 00..00..44,, 2288 DDeecceemmbbeerr,, 11999977 +o SGML-Tools 1.0.2 used for formatting. +o Some minor tweaks. 00..00..55,, 11 JJaannuuaarryy,, 11999988 +o SGML-Tools 1.0.2 formatting problems solved. +o Long #define tables removed. +o More references to the source. 00..00..66,, 1122 MMaarrcchh,, 11999988 +o Some details better. Many thanks to Kekoa Proundfoot (kekoa@graphics.stanford.edu). +o PlanetQuake is the new home. +o Compatible with Quake II up to version 3.14. +o SGML-Tools 1.0.5 used. 11..00..00,, 1177 JJuunnee,, 11999988 +o Thanks to Ben Swartzlander (swartz@rice.edu) for the config string index 30. +o Changed some command names (sound volume, server protocol version, config string, print text). +o Changed the coordinates in playerinfo to standard coordinates. +o Most changes in variable types and names were necessary to reduce the total number of tokens in the Quake and Quake II text parser of LMPC. +o Compatible with Quake II up to 3.15a. 11..00..11,, 1155 JJuullyy,, 11999988 +o server side recording information included. +o Compatible with Quake II up to 3.17. +o Some hints on old_origin updates and sequence numbering. +o SGML-Tools 1.0.7 used.